{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## An ML model learn to perform equalization "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import packages from other direction. Itis necessary if the project is structured as:\n",
    "# my_project\n",
    "# ├── notebooks\n",
    "# │   └── Generate Radio Data for tr.ipynb\n",
    "# ├── local_python_package\n",
    "# │   ├── __init__.py\n",
    "# │   ├── models.py\n",
    "# ├── README.md\n",
    "import os\n",
    "import sys\n",
    "module_path = os.path.abspath(os.path.join('..'))\n",
    "if module_path not in sys.path:\n",
    "    sys.path.append(module_path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import multiprocessing as mp\n",
    "import numpy as np\n",
    "\n",
    "import tensorflow as tf\n",
    "from radioml.dataset import RadioDataGenerator\n",
    "\n",
    "# For visualization\n",
    "from IPython.display import SVG, display\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "import pylab"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paramters"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "DATA_LEN = 200\n",
    "PREAMBLE_LEN = 40\n",
    "CHANNEL_LEN = 2\n",
    "\n",
    "SNR_TRAIN = 20.0\n",
    "OMEGA_TRAIN = 1/50"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Construct data generator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "radio = RadioDataGenerator(data_len    =DATA_LEN, \n",
    "                           preamble_len=PREAMBLE_LEN, \n",
    "                           channels_len=CHANNEL_LEN,\n",
    "                           modulation_scheme='QPSK')\n",
    "training_generator = radio.equalization_data_generator(OMEGA_TRAIN, \n",
    "                                                       SNR_TRAIN, \n",
    "                                                       batch_size=256, \n",
    "                                                       num_cpus=8)\n",
    "validation_generator = radio.equalization_data_generator(OMEGA_TRAIN, \n",
    "                                                         SNR_TRAIN, \n",
    "                                                         batch_size=256, \n",
    "                                                         seed=2018)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# # Visualize a few examples\n",
    "# examples = next(radio.equalization_data_generator(OMEGA_TRAIN, \n",
    "#                                                   SNR_TRAIN, \n",
    "#                                                   batch_size=4, \n",
    "#                                                   num_cpus=8))\n",
    "# [preambles,corrected_preamble, corrected_data], equalized_packet = examples\n",
    "# def visualize(data):\n",
    "#     a = plt.scatter(data[...,0].flatten(),\n",
    "#                 data[...,1].flatten())\n",
    "#     plt.axhline()\n",
    "#     plt.axvline()\n",
    "#     return a\n",
    "    \n",
    "# a = visualize(corrected_data)\n",
    "# b = visualize(equalized_packet)\n",
    "\n",
    "# plt.legend((a, b), ('Before Equalization', 'After Equalization'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Number of training parameters: 215464\n"
     ]
    },
    {
     "data": {
      "image/svg+xml": [
       "<svg height=\"968pt\" viewBox=\"0.00 0.00 862.50 968.00\" width=\"863pt\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g class=\"graph\" id=\"graph0\" transform=\"scale(1 1) rotate(0) translate(4 964)\">\n",
       "<title>G</title>\n",
       "<polygon fill=\"white\" points=\"-4,4 -4,-964 858.5,-964 858.5,4 -4,4\" stroke=\"none\"/>\n",
       "<!-- 139785877550248 -->\n",
       "<g class=\"node\" id=\"node1\"><title>139785877550248</title>\n",
       "<polygon fill=\"none\" points=\"0,-913.5 0,-959.5 281,-959.5 281,-913.5 0,-913.5\" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"68\" y=\"-932.8\">Preamble: InputLayer</text>\n",
       "<polyline fill=\"none\" points=\"136,-913.5 136,-959.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"163.5\" y=\"-944.3\">input:</text>\n",
       "<polyline fill=\"none\" points=\"136,-936.5 191,-936.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"163.5\" y=\"-921.3\">output:</text>\n",
       "<polyline fill=\"none\" points=\"191,-913.5 191,-959.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"236\" y=\"-944.3\">(None, 40, 2)</text>\n",
       "<polyline fill=\"none\" points=\"191,-936.5 281,-936.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"236\" y=\"-921.3\">(None, 40, 2)</text>\n",
       "</g>\n",
       "<!-- 139785877552712 -->\n",
       "<g class=\"node\" id=\"node3\"><title>139785877552712</title>\n",
       "<polygon fill=\"none\" points=\"68.5,-830.5 68.5,-876.5 584.5,-876.5 584.5,-830.5 68.5,-830.5\" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"209\" y=\"-849.8\">Preamble_CFOCorrectedPreamble: Concatenate</text>\n",
       "<polyline fill=\"none\" points=\"349.5,-830.5 349.5,-876.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"377\" y=\"-861.3\">input:</text>\n",
       "<polyline fill=\"none\" points=\"349.5,-853.5 404.5,-853.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"377\" y=\"-838.3\">output:</text>\n",
       "<polyline fill=\"none\" points=\"404.5,-830.5 404.5,-876.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"494.5\" y=\"-861.3\">[(None, 40, 2), (None, 40, 2)]</text>\n",
       "<polyline fill=\"none\" points=\"404.5,-853.5 584.5,-853.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"494.5\" y=\"-838.3\">(None, 40, 4)</text>\n",
       "</g>\n",
       "<!-- 139785877550248&#45;&gt;139785877552712 -->\n",
       "<g class=\"edge\" id=\"edge1\"><title>139785877550248-&gt;139785877552712</title>\n",
       "<path d=\"M191.325,-913.366C214.495,-903.277 242.095,-891.257 266.296,-880.718\" fill=\"none\" stroke=\"black\"/>\n",
       "<polygon fill=\"black\" points=\"267.965,-883.808 275.736,-876.607 265.17,-877.391 267.965,-883.808\" stroke=\"black\"/>\n",
       "</g>\n",
       "<!-- 139785877550864 -->\n",
       "<g class=\"node\" id=\"node2\"><title>139785877550864</title>\n",
       "<polygon fill=\"none\" points=\"299.5,-913.5 299.5,-959.5 725.5,-959.5 725.5,-913.5 299.5,-913.5\" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"440\" y=\"-932.8\">CFOCorrected_PreambleConvolved: InputLayer</text>\n",
       "<polyline fill=\"none\" points=\"580.5,-913.5 580.5,-959.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"608\" y=\"-944.3\">input:</text>\n",
       "<polyline fill=\"none\" points=\"580.5,-936.5 635.5,-936.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"608\" y=\"-921.3\">output:</text>\n",
       "<polyline fill=\"none\" points=\"635.5,-913.5 635.5,-959.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"680.5\" y=\"-944.3\">(None, 40, 2)</text>\n",
       "<polyline fill=\"none\" points=\"635.5,-936.5 725.5,-936.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"680.5\" y=\"-921.3\">(None, 40, 2)</text>\n",
       "</g>\n",
       "<!-- 139785877550864&#45;&gt;139785877552712 -->\n",
       "<g class=\"edge\" id=\"edge2\"><title>139785877550864-&gt;139785877552712</title>\n",
       "<path d=\"M461.675,-913.366C438.505,-903.277 410.905,-891.257 386.704,-880.718\" fill=\"none\" stroke=\"black\"/>\n",
       "<polygon fill=\"black\" points=\"387.83,-877.391 377.264,-876.607 385.035,-883.808 387.83,-877.391\" stroke=\"black\"/>\n",
       "</g>\n",
       "<!-- 139788581802728 -->\n",
       "<g class=\"node\" id=\"node4\"><title>139788581802728</title>\n",
       "<polygon fill=\"none\" points=\"206,-747.5 206,-793.5 447,-793.5 447,-747.5 206,-747.5\" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"254\" y=\"-766.8\">flatten: Flatten</text>\n",
       "<polyline fill=\"none\" points=\"302,-747.5 302,-793.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"329.5\" y=\"-778.3\">input:</text>\n",
       "<polyline fill=\"none\" points=\"302,-770.5 357,-770.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"329.5\" y=\"-755.3\">output:</text>\n",
       "<polyline fill=\"none\" points=\"357,-747.5 357,-793.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"402\" y=\"-778.3\">(None, 40, 4)</text>\n",
       "<polyline fill=\"none\" points=\"357,-770.5 447,-770.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"402\" y=\"-755.3\">(None, 160)</text>\n",
       "</g>\n",
       "<!-- 139785877552712&#45;&gt;139788581802728 -->\n",
       "<g class=\"edge\" id=\"edge3\"><title>139785877552712-&gt;139788581802728</title>\n",
       "<path d=\"M326.5,-830.366C326.5,-822.152 326.5,-812.658 326.5,-803.725\" fill=\"none\" stroke=\"black\"/>\n",
       "<polygon fill=\"black\" points=\"330,-803.607 326.5,-793.607 323,-803.607 330,-803.607\" stroke=\"black\"/>\n",
       "</g>\n",
       "<!-- 139788651810544 -->\n",
       "<g class=\"node\" id=\"node5\"><title>139788651810544</title>\n",
       "<polygon fill=\"none\" points=\"139.5,-664.5 139.5,-710.5 513.5,-710.5 513.5,-664.5 139.5,-664.5\" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"257.5\" y=\"-683.8\">ChannelEstimationNet_Dense_1: Dense</text>\n",
       "<polyline fill=\"none\" points=\"375.5,-664.5 375.5,-710.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"403\" y=\"-695.3\">input:</text>\n",
       "<polyline fill=\"none\" points=\"375.5,-687.5 430.5,-687.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"403\" y=\"-672.3\">output:</text>\n",
       "<polyline fill=\"none\" points=\"430.5,-664.5 430.5,-710.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"472\" y=\"-695.3\">(None, 160)</text>\n",
       "<polyline fill=\"none\" points=\"430.5,-687.5 513.5,-687.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"472\" y=\"-672.3\">(None, 300)</text>\n",
       "</g>\n",
       "<!-- 139788581802728&#45;&gt;139788651810544 -->\n",
       "<g class=\"edge\" id=\"edge4\"><title>139788581802728-&gt;139788651810544</title>\n",
       "<path d=\"M326.5,-747.366C326.5,-739.152 326.5,-729.658 326.5,-720.725\" fill=\"none\" stroke=\"black\"/>\n",
       "<polygon fill=\"black\" points=\"330,-720.607 326.5,-710.607 323,-720.607 330,-720.607\" stroke=\"black\"/>\n",
       "</g>\n",
       "<!-- 139785877703592 -->\n",
       "<g class=\"node\" id=\"node6\"><title>139785877703592</title>\n",
       "<polygon fill=\"none\" points=\"139.5,-581.5 139.5,-627.5 513.5,-627.5 513.5,-581.5 139.5,-581.5\" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"257.5\" y=\"-600.8\">ChannelEstimationNet_Dense_2: Dense</text>\n",
       "<polyline fill=\"none\" points=\"375.5,-581.5 375.5,-627.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"403\" y=\"-612.3\">input:</text>\n",
       "<polyline fill=\"none\" points=\"375.5,-604.5 430.5,-604.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"403\" y=\"-589.3\">output:</text>\n",
       "<polyline fill=\"none\" points=\"430.5,-581.5 430.5,-627.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"472\" y=\"-612.3\">(None, 300)</text>\n",
       "<polyline fill=\"none\" points=\"430.5,-604.5 513.5,-604.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"472\" y=\"-589.3\">(None, 300)</text>\n",
       "</g>\n",
       "<!-- 139788651810544&#45;&gt;139785877703592 -->\n",
       "<g class=\"edge\" id=\"edge5\"><title>139788651810544-&gt;139785877703592</title>\n",
       "<path d=\"M326.5,-664.366C326.5,-656.152 326.5,-646.658 326.5,-637.725\" fill=\"none\" stroke=\"black\"/>\n",
       "<polygon fill=\"black\" points=\"330,-637.607 326.5,-627.607 323,-637.607 330,-637.607\" stroke=\"black\"/>\n",
       "</g>\n",
       "<!-- 139785877295288 -->\n",
       "<g class=\"node\" id=\"node7\"><title>139785877295288</title>\n",
       "<polygon fill=\"none\" points=\"182,-498.5 182,-544.5 471,-544.5 471,-498.5 182,-498.5\" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"257.5\" y=\"-517.8\">ChannelEstimate: Dense</text>\n",
       "<polyline fill=\"none\" points=\"333,-498.5 333,-544.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"360.5\" y=\"-529.3\">input:</text>\n",
       "<polyline fill=\"none\" points=\"333,-521.5 388,-521.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"360.5\" y=\"-506.3\">output:</text>\n",
       "<polyline fill=\"none\" points=\"388,-498.5 388,-544.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"429.5\" y=\"-529.3\">(None, 300)</text>\n",
       "<polyline fill=\"none\" points=\"388,-521.5 471,-521.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"429.5\" y=\"-506.3\">(None, 2)</text>\n",
       "</g>\n",
       "<!-- 139785877703592&#45;&gt;139785877295288 -->\n",
       "<g class=\"edge\" id=\"edge6\"><title>139785877703592-&gt;139785877295288</title>\n",
       "<path d=\"M326.5,-581.366C326.5,-573.152 326.5,-563.658 326.5,-554.725\" fill=\"none\" stroke=\"black\"/>\n",
       "<polygon fill=\"black\" points=\"330,-554.607 326.5,-544.607 323,-554.607 330,-554.607\" stroke=\"black\"/>\n",
       "</g>\n",
       "<!-- 139785877169544 -->\n",
       "<g class=\"node\" id=\"node9\"><title>139785877169544</title>\n",
       "<polygon fill=\"none\" points=\"164.5,-415.5 164.5,-461.5 488.5,-461.5 488.5,-415.5 164.5,-415.5\" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"250.5\" y=\"-434.8\">repeat_vector: RepeatVector</text>\n",
       "<polyline fill=\"none\" points=\"336.5,-415.5 336.5,-461.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"364\" y=\"-446.3\">input:</text>\n",
       "<polyline fill=\"none\" points=\"336.5,-438.5 391.5,-438.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"364\" y=\"-423.3\">output:</text>\n",
       "<polyline fill=\"none\" points=\"391.5,-415.5 391.5,-461.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"440\" y=\"-446.3\">(None, 2)</text>\n",
       "<polyline fill=\"none\" points=\"391.5,-438.5 488.5,-438.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"440\" y=\"-423.3\">(None, 200, 2)</text>\n",
       "</g>\n",
       "<!-- 139785877295288&#45;&gt;139785877169544 -->\n",
       "<g class=\"edge\" id=\"edge7\"><title>139785877295288-&gt;139785877169544</title>\n",
       "<path d=\"M326.5,-498.366C326.5,-490.152 326.5,-480.658 326.5,-471.725\" fill=\"none\" stroke=\"black\"/>\n",
       "<polygon fill=\"black\" points=\"330,-471.607 326.5,-461.607 323,-471.607 330,-471.607\" stroke=\"black\"/>\n",
       "</g>\n",
       "<!-- 139785877550752 -->\n",
       "<g class=\"node\" id=\"node8\"><title>139785877550752</title>\n",
       "<polygon fill=\"none\" points=\"506.5,-415.5 506.5,-461.5 854.5,-461.5 854.5,-415.5 506.5,-415.5\" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"604.5\" y=\"-434.8\">CFOCorrected_Data: InputLayer</text>\n",
       "<polyline fill=\"none\" points=\"702.5,-415.5 702.5,-461.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"730\" y=\"-446.3\">input:</text>\n",
       "<polyline fill=\"none\" points=\"702.5,-438.5 757.5,-438.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"730\" y=\"-423.3\">output:</text>\n",
       "<polyline fill=\"none\" points=\"757.5,-415.5 757.5,-461.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"806\" y=\"-446.3\">(None, 200, 2)</text>\n",
       "<polyline fill=\"none\" points=\"757.5,-438.5 854.5,-438.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"806\" y=\"-423.3\">(None, 200, 2)</text>\n",
       "</g>\n",
       "<!-- 139785876768808 -->\n",
       "<g class=\"node\" id=\"node10\"><title>139785876768808</title>\n",
       "<polygon fill=\"none\" points=\"231,-332.5 231,-378.5 776,-378.5 776,-332.5 231,-332.5\" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"379\" y=\"-351.8\">CFOCorrectedData_ChannelEstimate: Concatenate</text>\n",
       "<polyline fill=\"none\" points=\"527,-332.5 527,-378.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"554.5\" y=\"-363.3\">input:</text>\n",
       "<polyline fill=\"none\" points=\"527,-355.5 582,-355.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"554.5\" y=\"-340.3\">output:</text>\n",
       "<polyline fill=\"none\" points=\"582,-332.5 582,-378.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"679\" y=\"-363.3\">[(None, 200, 2), (None, 200, 2)]</text>\n",
       "<polyline fill=\"none\" points=\"582,-355.5 776,-355.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"679\" y=\"-340.3\">(None, 200, 4)</text>\n",
       "</g>\n",
       "<!-- 139785877550752&#45;&gt;139785876768808 -->\n",
       "<g class=\"edge\" id=\"edge8\"><title>139785877550752-&gt;139785876768808</title>\n",
       "<path d=\"M632.134,-415.366C610.183,-405.321 584.054,-393.363 561.097,-382.858\" fill=\"none\" stroke=\"black\"/>\n",
       "<polygon fill=\"black\" points=\"562.357,-379.586 551.808,-378.607 559.444,-385.951 562.357,-379.586\" stroke=\"black\"/>\n",
       "</g>\n",
       "<!-- 139785877169544&#45;&gt;139785876768808 -->\n",
       "<g class=\"edge\" id=\"edge9\"><title>139785877169544-&gt;139785876768808</title>\n",
       "<path d=\"M374.866,-415.366C396.817,-405.321 422.946,-393.363 445.903,-382.858\" fill=\"none\" stroke=\"black\"/>\n",
       "<polygon fill=\"black\" points=\"447.556,-385.951 455.192,-378.607 444.643,-379.586 447.556,-385.951\" stroke=\"black\"/>\n",
       "</g>\n",
       "<!-- 139785876902856 -->\n",
       "<g class=\"node\" id=\"node11\"><title>139785876902856</title>\n",
       "<polygon fill=\"none\" points=\"260,-249.5 260,-295.5 747,-295.5 747,-249.5 260,-249.5\" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"424\" y=\"-268.8\">Equalizationnet_BiLSTM_1(lstm): Bidirectional(LSTM)</text>\n",
       "<polyline fill=\"none\" points=\"588,-249.5 588,-295.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"615.5\" y=\"-280.3\">input:</text>\n",
       "<polyline fill=\"none\" points=\"588,-272.5 643,-272.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"615.5\" y=\"-257.3\">output:</text>\n",
       "<polyline fill=\"none\" points=\"643,-249.5 643,-295.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"695\" y=\"-280.3\">(None, 200, 4)</text>\n",
       "<polyline fill=\"none\" points=\"643,-272.5 747,-272.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"695\" y=\"-257.3\">(None, 200, 90)</text>\n",
       "</g>\n",
       "<!-- 139785876768808&#45;&gt;139785876902856 -->\n",
       "<g class=\"edge\" id=\"edge10\"><title>139785876768808-&gt;139785876902856</title>\n",
       "<path d=\"M503.5,-332.366C503.5,-324.152 503.5,-314.658 503.5,-305.725\" fill=\"none\" stroke=\"black\"/>\n",
       "<polygon fill=\"black\" points=\"507,-305.607 503.5,-295.607 500,-305.607 507,-305.607\" stroke=\"black\"/>\n",
       "</g>\n",
       "<!-- 139785859497936 -->\n",
       "<g class=\"node\" id=\"node12\"><title>139785859497936</title>\n",
       "<polygon fill=\"none\" points=\"253.5,-166.5 253.5,-212.5 753.5,-212.5 753.5,-166.5 253.5,-166.5\" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"424\" y=\"-185.8\">Equalizationnet_BiLSTM_2(lstm_1): Bidirectional(LSTM)</text>\n",
       "<polyline fill=\"none\" points=\"594.5,-166.5 594.5,-212.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"622\" y=\"-197.3\">input:</text>\n",
       "<polyline fill=\"none\" points=\"594.5,-189.5 649.5,-189.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"622\" y=\"-174.3\">output:</text>\n",
       "<polyline fill=\"none\" points=\"649.5,-166.5 649.5,-212.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"701.5\" y=\"-197.3\">(None, 200, 90)</text>\n",
       "<polyline fill=\"none\" points=\"649.5,-189.5 753.5,-189.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"701.5\" y=\"-174.3\">(None, 200, 90)</text>\n",
       "</g>\n",
       "<!-- 139785876902856&#45;&gt;139785859497936 -->\n",
       "<g class=\"edge\" id=\"edge11\"><title>139785876902856-&gt;139785859497936</title>\n",
       "<path d=\"M503.5,-249.366C503.5,-241.152 503.5,-231.658 503.5,-222.725\" fill=\"none\" stroke=\"black\"/>\n",
       "<polygon fill=\"black\" points=\"507,-222.607 503.5,-212.607 500,-222.607 507,-222.607\" stroke=\"black\"/>\n",
       "</g>\n",
       "<!-- 139785854955360 -->\n",
       "<g class=\"node\" id=\"node13\"><title>139785854955360</title>\n",
       "<polygon fill=\"none\" points=\"253.5,-83.5 253.5,-129.5 753.5,-129.5 753.5,-83.5 253.5,-83.5\" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"421\" y=\"-102.8\">Equalizationnet_Dense_1(dense): TimeDistributed(Dense)</text>\n",
       "<polyline fill=\"none\" points=\"588.5,-83.5 588.5,-129.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"616\" y=\"-114.3\">input:</text>\n",
       "<polyline fill=\"none\" points=\"588.5,-106.5 643.5,-106.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"616\" y=\"-91.3\">output:</text>\n",
       "<polyline fill=\"none\" points=\"643.5,-83.5 643.5,-129.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"698.5\" y=\"-114.3\">(None, 200, 90)</text>\n",
       "<polyline fill=\"none\" points=\"643.5,-106.5 753.5,-106.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"698.5\" y=\"-91.3\">(None, 200, 100)</text>\n",
       "</g>\n",
       "<!-- 139785859497936&#45;&gt;139785854955360 -->\n",
       "<g class=\"edge\" id=\"edge12\"><title>139785859497936-&gt;139785854955360</title>\n",
       "<path d=\"M503.5,-166.366C503.5,-158.152 503.5,-148.658 503.5,-139.725\" fill=\"none\" stroke=\"black\"/>\n",
       "<polygon fill=\"black\" points=\"507,-139.607 503.5,-129.607 500,-139.607 507,-139.607\" stroke=\"black\"/>\n",
       "</g>\n",
       "<!-- 139785853366000 -->\n",
       "<g class=\"node\" id=\"node14\"><title>139785853366000</title>\n",
       "<polygon fill=\"none\" points=\"267.5,-0.5 267.5,-46.5 739.5,-46.5 739.5,-0.5 267.5,-0.5\" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"421\" y=\"-19.8\">Equalized_Packet(dense_1): TimeDistributed(Dense)</text>\n",
       "<polyline fill=\"none\" points=\"574.5,-0.5 574.5,-46.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"602\" y=\"-31.3\">input:</text>\n",
       "<polyline fill=\"none\" points=\"574.5,-23.5 629.5,-23.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"602\" y=\"-8.3\">output:</text>\n",
       "<polyline fill=\"none\" points=\"629.5,-0.5 629.5,-46.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"684.5\" y=\"-31.3\">(None, 200, 100)</text>\n",
       "<polyline fill=\"none\" points=\"629.5,-23.5 739.5,-23.5 \" stroke=\"black\"/>\n",
       "<text font-family=\"Times,serif\" font-size=\"14.00\" text-anchor=\"middle\" x=\"684.5\" y=\"-8.3\">(None, 200, 2)</text>\n",
       "</g>\n",
       "<!-- 139785854955360&#45;&gt;139785853366000 -->\n",
       "<g class=\"edge\" id=\"edge13\"><title>139785854955360-&gt;139785853366000</title>\n",
       "<path d=\"M503.5,-83.3664C503.5,-75.1516 503.5,-65.6579 503.5,-56.7252\" fill=\"none\" stroke=\"black\"/>\n",
       "<polygon fill=\"black\" points=\"507,-56.6068 503.5,-46.6068 500,-56.6069 507,-56.6068\" stroke=\"black\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>"
      ],
      "text/plain": [
       "<IPython.core.display.SVG object>"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from tensorflow.keras.layers import Dense, Bidirectional, LSTM, TimeDistributed, RepeatVector\n",
    "\n",
    "tf.keras.backend.clear_session()\n",
    "\n",
    "def channel_estimation(preamble, cfo_corrected_preamble, scope='ChannelEstimationNet'):\n",
    "    \"\"\"Channel Estimation.\"\"\"\n",
    "    with tf.name_scope('ChannelEstimationNet'):\n",
    "        inputs = tf.keras.layers.concatenate([preamble, cfo_corrected_preamble], axis=-1,\n",
    "                                            name='Preamble_CFOCorrectedPreamble')\n",
    "        inputs = tf.keras.layers.Flatten()(inputs)\n",
    "        x = Dense(300, 'relu', name='ChannelEstimationNet_Dense_1')(inputs)\n",
    "        x = Dense(300, 'relu', name='ChannelEstimationNet_Dense_2')(x)\n",
    "        x = Dense(CHANNEL_LEN, 'sigmoid', name='ChannelEstimate')(x)\n",
    "    return x\n",
    "\n",
    "\n",
    "def equalization_network(cfo_corrected_data, chan_est):\n",
    "    \"\"\"Given a [preamble, preambe_conv, data_conv] Predict equalized packet \"\"\"\n",
    "    \n",
    "    chan_est = RepeatVector(DATA_LEN)(chan_est)\n",
    "    inputs = tf.keras.layers.concatenate([cfo_corrected_data, chan_est], axis=-1,\n",
    "                                        name=\"CFOCorrectedData_ChannelEstimate\")\n",
    "    with tf.name_scope('EqualizationNet'):\n",
    "        scope='Equalizationnet'\n",
    "        x = Bidirectional(LSTM(45, return_sequences=True), \n",
    "                          name=scope+'_BiLSTM_1')(inputs)\n",
    "        x = Bidirectional(LSTM(45, return_sequences=True), \n",
    "                          name=scope+'_BiLSTM_2')(x)\n",
    "        x = TimeDistributed(Dense(100, activation='relu'), \n",
    "                            name=scope+'_Dense_1')(x)\n",
    "    x = TimeDistributed(Dense(2, activation='linear'), \n",
    "                        name='Equalized_Packet')(x)\n",
    "    return x\n",
    "\n",
    "\n",
    "# Think of keras.Input as tf.placeholder\n",
    "preamble               = tf.keras.layers.Input(shape=(PREAMBLE_LEN, 2), name='Preamble')\n",
    "cfo_corrected_data     = tf.keras.layers.Input(shape=(DATA_LEN, 2), name='CFOCorrected_Data')\n",
    "cfo_corrected_preamble = tf.keras.layers.Input(shape=(PREAMBLE_LEN, 2), name='CFOCorrected_PreambleConvolved')\n",
    "\n",
    "# Build tensorflow graph\n",
    "   \n",
    "channel_est = channel_estimation(preamble, cfo_corrected_preamble)\n",
    "\n",
    "                  \n",
    "model = tf.keras.Model(inputs=[preamble, cfo_corrected_preamble], outputs= channel_est)\n",
    "model.trainable = True             \n",
    "                  \n",
    "equalized_packet = equalization_network(cfo_corrected_data, channel_est)\n",
    "\n",
    "model = tf.keras.Model(inputs=[preamble, cfo_corrected_preamble, cfo_corrected_data], \n",
    "                       outputs=equalized_packet)\n",
    "\n",
    "print(\"Number of training parameters: %d\" % model.count_params())\n",
    "tf.keras.utils.plot_model(model, \"equalization.svg\", show_shapes=True, show_layer_names=True)\n",
    "SVG(\"equalization.svg\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/50\n",
      "200/200 [==============================] - 201s 1s/step - loss: 0.0103 - val_loss: 0.0101\n",
      "Epoch 2/50\n",
      "200/200 [==============================] - 193s 967ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 3/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 4/50\n",
      "200/200 [==============================] - 193s 965ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 5/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0103\n",
      "Epoch 6/50\n",
      "200/200 [==============================] - 193s 967ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 7/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0101\n",
      "Epoch 8/50\n",
      "200/200 [==============================] - 193s 967ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 9/50\n",
      "200/200 [==============================] - 193s 965ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 10/50\n",
      "200/200 [==============================] - 193s 967ms/step - loss: 0.0103 - val_loss: 0.0102\n",
      "Epoch 11/50\n",
      "200/200 [==============================] - 193s 967ms/step - loss: 0.0103 - val_loss: 0.0102\n",
      "Epoch 12/50\n",
      "200/200 [==============================] - 193s 965ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 13/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 14/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 15/50\n",
      "200/200 [==============================] - 193s 965ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 16/50\n",
      "200/200 [==============================] - 193s 965ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 17/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 18/50\n",
      "200/200 [==============================] - 193s 965ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 19/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 20/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 21/50\n",
      "200/200 [==============================] - 193s 965ms/step - loss: 0.0102 - val_loss: 0.0103\n",
      "Epoch 22/50\n",
      "200/200 [==============================] - 193s 964ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 23/50\n",
      "200/200 [==============================] - 193s 964ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 24/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 25/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 26/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 27/50\n",
      "200/200 [==============================] - 193s 965ms/step - loss: 0.0102 - val_loss: 0.0101\n",
      "Epoch 28/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 29/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0101\n",
      "Epoch 30/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0103\n",
      "Epoch 31/50\n",
      "200/200 [==============================] - 193s 967ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 32/50\n",
      "200/200 [==============================] - 193s 965ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 33/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0103\n",
      "Epoch 34/50\n",
      "200/200 [==============================] - 193s 965ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 35/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 36/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 37/50\n",
      "200/200 [==============================] - 193s 965ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 38/50\n",
      "200/200 [==============================] - 193s 964ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 39/50\n",
      "200/200 [==============================] - 193s 965ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 40/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 41/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 42/50\n",
      "200/200 [==============================] - 193s 966ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 43/50\n",
      "200/200 [==============================] - 195s 973ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 44/50\n",
      "200/200 [==============================] - 194s 968ms/step - loss: 0.0104 - val_loss: 0.0102\n",
      "Epoch 45/50\n",
      "200/200 [==============================] - 194s 968ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 46/50\n",
      "200/200 [==============================] - 194s 968ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 47/50\n",
      "200/200 [==============================] - 193s 967ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 48/50\n",
      "200/200 [==============================] - 193s 967ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 49/50\n",
      "200/200 [==============================] - 194s 969ms/step - loss: 0.0102 - val_loss: 0.0102\n",
      "Epoch 50/50\n",
      "200/200 [==============================] - 193s 967ms/step - loss: 0.0102 - val_loss: 0.0102\n"
     ]
    }
   ],
   "source": [
    "model.compile('adam','mse')\n",
    "model.load_weights('../models/equalization4.hdf5')\n",
    "history = model.fit_generator(\n",
    "    generator=training_generator,\n",
    "    validation_data=validation_generator,\n",
    "    steps_per_epoch=200,\n",
    "    validation_steps=10,\n",
    "    callbacks=[tf.keras.callbacks.ModelCheckpoint('../models/equalization4.hdf5', save_best_only=True)],\n",
    "    epochs=50)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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d0un9LijltLSUofRBlixYgUzZ85k5syZ7Nmzh927d/e5T1JSEueddx4As2bNorKy0u1jX3rppX2OWb16NVdeKUYWTp8+ncmTAzvhSCTRSo/HHZ2GOyQetzfPOBQFOM6tFsvLy3nsscdYv349GRkZXH311W7zpOPj4x2/x8TEYLG437IlJCT4PEYikfhHtEsl0uN2oaWlhdTUVNLS0qipqeGDDz4I+nPMnz+fl19+GYAdO3a49eglEolnWs1WAFo6pMctAWbOnMmkSZOYMGECI0eOZP78+UF/jttvv51rr72WSZMmOX7S09OD/jwSyVCl1SwMdrR63FFpuB944AHH72PGjOmV0aEoCs8//7zb+61evdrxe1NTk+P3K6+80qFZP/jgg26Pz8/P58CBA4BoDPXiiy+SmJhIeXk5S5Ysobi4mI6Ojv79YxJJlNBm97il4ZYMGq2trSxevBiLxYKqqjz11FPExsq3QiLRS7RnlUhrEQIyMjLYtGlTqJchkUQsMjgpkUgkEUa0e9zScEskkoijrUsY7lazBZtNDfFqBh9puCUSScShBSdVFVq7ok8ukYZbIpFEHK1mC1kpogguGnXuqDHcixYt6lNM8+ijj3LLLbd4vI/RaASgurqaZcuWuT1m4cKFbNy40etzP/roo7S3tzv+Pv/883ulE0okEv10W210WWwUpCcC0VmEEzWGe/ny5bz00ku9rnvppZdYvny5z/sWFhbyyiuvBPzcrob7vffeIyMjI+DHk0iiGS2jRDPc0uMewixbtox3333XMTShsrKS6upqZsyYweLFi5k5cyZTp07lzTff7HPfyspKpkyZAkBHRwdXXnklEydO5JJLLulVNHPLLbc42sHef//9ADz++ONUV1ezaNEiFi1aBEBJSQknT54E4JFHHmHKlClMmTKFJ5980vF8EydO5KabbmLy5MksWbJEFudIJHY0Q53vMNzR53GHJo975b1wfIfbm5KsFogJYFn5U+G8hz3enJWVxdy5c1m5ciVLly7lpZde4oorriApKYnXX3+dtLQ0Tp48ybx587jooos8znL8y1/+QnJyMnv27GH79u29WrI+9NBDZGVlYbVaWbx4Mdu3b+eOO+7gkUce4ZNPPiEnJ6fXY23atIlnn32WdevWoaoqc+bM4dxzzyUzM5Py8nJWrFjB3//+d6644gpeffVVrr76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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Source: https://machinelearningmastery.com/display-deep-learning-model-training-history-in-keras/\n",
    "plt.plot(history.history['loss'])\n",
    "plt.plot(history.history['val_loss'])\n",
    "plt.title('model loss')\n",
    "plt.ylabel('Loss')\n",
    "plt.xlabel('Epoch')\n",
    "plt.legend(['Training', 'Validation'], loc='upper left')\n",
    "plt.grid()\n",
    "plt.semilogy()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'visualize' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-9-04953f205c7c>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      7\u001b[0m \u001b[0mpredictions\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpredict\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minputs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      8\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 9\u001b[0;31m \u001b[0mc\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mvisualize\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minputs\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     10\u001b[0m \u001b[0ma\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mvisualize\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpredictions\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     11\u001b[0m \u001b[0mb\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mvisualize\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlabels\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mNameError\u001b[0m: name 'visualize' is not defined"
     ]
    }
   ],
   "source": [
    "inputs, labels = next(radio.equalization_data_generator(OMEGA_TRAIN, \n",
    "                                                  SNR_TRAIN, \n",
    "                                                  batch_size=16, \n",
    "                                                  num_cpus=16,\n",
    "                                                  seed=2020))\n",
    "\n",
    "predictions = model.predict(inputs)\n",
    "\n",
    "c = visualize(inputs[-1])\n",
    "a = visualize(predictions)\n",
    "b = visualize(labels)\n",
    "\n",
    "plt.legend((a, b), ('Predictions', 'Labels', 'Inputs'))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "deepcom",
   "language": "python",
   "name": "deepcom"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
